# $Id: lexer.py 9227e81e919a 2012/08/27 15:49:57 pgurumur $
# Copyright (c) 2009 Prabhu Gurumurthy  <pgurumur@gmail.com>
# 
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

__version__  = "$Revision: 9227e81e919a $"
__author__   = "$Author: pgurumur $"
__modified__ = "$Date: 2012-08-27 15:49:57Z $"

# essential imports
import os

# third party imports
from pyparsing import *

# Local imports
import ip
import stdlib

suppress = lambda word: Keyword(word).suppress()
grouping = lambda key, obj: Group(key + obj)

class LexerError(Exception):
   pass

class LexerAssert(Exception):
   pass

#function for checking the validity of IP/IPv6 addresses
def CheckIP(String, Location, Token):
   temp = None
   try:
      temp = ip.ip(Token[0])
   except IPError, message:
      raise LexerAssert, message

   return Token

# Main parser object, takes in a filename
class Lexer(object):
   def __init__ (self, Filename):
      # instantiate the error classes
      self._err = LexerError()
      self._assert = LexerAssert()

      # object defines
      self._file = None
      self._rule = None
      self._lb = None
      self._rb = None
      self._lp = None
      self._rp = None
      self._lbk = None
      self._rbk = None
      self._lt = None
      self._gt = None
      self._ipv4 = None
      self._ipv6 = None
      self._ipv4Net = None
      self._ipv6Net = None
      self._mac = None
      self._quoted = None
      self._hexval = None

      # Check to see whether filename parameter is present, if it is not
      # raise exeception
      if Filename:
         self._file = Filename
      else:
         self._error("no filename provided to parse")

      # Chcek to see whether the file exists within the system, nd we are
      # able to open it, if either of the two fails, raise exception
      if os.path.exists(self._file):
         try:
            self._fh = open(self._file, "r")
         except IOError, message:
            self._error(message)
      else:
         self._error("%s: does not exist" %self._file)

      # Most commonly use identifiers, keywords
      self._hexval = "abcdefABCDEF0123456789"

      self._lb, self._rb = map(Suppress, "{}")
      self._lp, self._rp = map(Suppress, "()")
      self._lbk, self._rbk = map(Suppress, "[]")
      self._lt, self._gt = map(Suppress, "<>")

      self._quoted = quotedString.copy().setParseAction(removeQuotes)
      self._ipv4 = Word(nums + ".").setParseAction(CheckIP)
      self._ipv6 = Word(self._hexval + ":").setParseAction(CheckIP)
      self._ipv4Net = Word(nums + "." + "/").setParseAction(CheckIP)
      self._ipv6Net = Word(self._hexval + ":" + "/").setParseAction(CheckIP)
      self._mac = Word(self._hexval + ":").setParseAction(self.checkMac)

   def __str__(self):
      return "parser class using config file %s" %self._file

   # Parse function, reads the entire file and returns the list
   # can raise any one of the execpt clsss errors
   def parse(self):
      result = None
      if self._fh and self._rule:
         try:
            result = self._rule.parseString(self._fh.read())
         except ParseFatalException, err:
            self._error(
                  stdlib.sprintf("parsing failed file: %s,  %s",
                  self._file, err))
         except ParseException, err:
            self._error(
                  stdlib.sprintf("parsing failed file: %s,  %s",
               self._file, err))
         except LexerAssert, err:
            self._error(
                  stdlib.sprintf("parsing failed file: %s,  %s",
               self_file, err))

         self.Close()

      return result

   # Close the file handle, if it is open
   def Close(self):
      if self._fh:
         self._fh.close()

   # Raise error
   def _error(self, Message):
      self.Close()
      if Message:
         raise LexerError, Message

# Instantiation of parser class
# Authentication parser used for RADIUS and LOCAL configuration
class AuthParser(Lexer):
   def __init__(self, Filename):
      super(AuthParser, self).__init__(Filename)
      deny = Keyword("deny")
      auth = suppress("auth")
      admin = Keyword("admin")
      radius = Keyword("radius")
      server = dictOf("server", self._quoted)
      secret = dictOf("secret", self._quoted)
      timeout = dictOf("timeout", Word(nums))
      local = Keyword("local")
      userAuth = self._quoted + Literal("=").suppress() + self._quoted
      username = self._quoted
      ignoreChar = "#"

      denyRule = Optional(dictOf(deny, Group(self._lb + ZeroOrMore(username) +
         self._rb)))
      adminRule = Optional(dictOf(admin, Group(self._lb + ZeroOrMore(
         self._quoted) + self._rb)))
      authRule = auth + self._lb + dictOf(radius, Group(self._lb +
         server + secret + timeout + self._rb)) + Optional(dictOf(local, Group(
            self._lb + OneOrMore(Group(userAuth)) + self._rb)))

      self._rule = denyRule + adminRule + authRule
      self._rule.ignore(Literal("\\") + LineEnd())
      self._rule.ignore(Literal(ignoreChar) + restOfLine)

class SQLCfgParser(Lexer):
   def __init__(self, Filename, Type = 1):
      super(SQLCfgParser, self).__init__(Filename)
      if Type == 1:
         default = Optional(Suppress(";")) + Suppress("set") + Suppress("dbase")
         host = default + Suppress("server_IP") + Word(alphas)
         db = default + Suppress("name") + Word(alphas)
         user = default + Suppress("user") + Word(alphas)
         passwd = default + Suppress("password") + self._quoted
      elif Type == 2:
         db = Keyword("database") + self._quoted
         username = Keyword("username") + Suppress("=") + Word(alphanums)
         password = Keyword("password") + Suppress("=") + self._quoted
         hostname = Keyword("hostname") + Suppress("=") + Word(
               alphanums + "." + "-" + "_")
         self._rule = db + self._lb + username + password + hostname + self._rb

      ignoreChar = "#"
      self._rule = host + db + user + passwd
      self._rule.ignore(Literal("\\") + LineEnd())
      self._rule.ignore(Literal(ignoreChar) + restOfLine)

class SwitchConfigParser(Lexer):
   def __init__(self, Filename):
      super(SwitchConfigParser, self).__init__(Filename)
      enable = Group(Keyword("enable") + self._quoted)
      loopback = Group(Keyword("loopback") + Word(nums) + self._ipv4Net)
      radius = Group(Keyword("radius-server") + self._quoted)
      domain = Group(Keyword("domain") + self._quoted)
      vtpdomain = Group(Keyword("vtpdomain") + self._quoted)
      vtppasswd = Group(Keyword("vtppasswd") + self._quoted)
      vlanip = Group(Keyword("vlanip") + self._ipv4Net)
      defgw = Group(Keyword("defgw") + self._ipv4)
      logserver = Group(Keyword("logserver") + self._ipv4)
      snmpread = Group(Keyword("snmpread") + self._quoted)
      snmpwrite = Group(Keyword("snmpwrite") + self._quoted)
      location = Group(Keyword("location") + self._quoted)
      contact = Group(Keyword("contact") + self._quoted)
      ntpserver = Group(Keyword("ntpserver") + self._ipv4)

      self._rule = OneOrMore(
            self._quoted + self._lb + enable + loopback + radius + \
            domain + vtpdomain + vtppasswd + vlanip + defgw + logserver + \
            snmpread + snmpwrite + location + contact + ntpserver)

      ignoreChar = "#"
      self._rule.ignore(Literal("\\") + LineEnd())
      self._rule.ignore(Literal(ignoreChar) + restOfLine)
